from minio import Minio
from minio.error import S3Error
from typing import Optional, Union, BinaryIO
import os
from pathlib import Path
from utils.logger import get_logger

logger = get_logger(__name__)


class MinioClient:
    def __init__(
        self,
        endpoint: str,
        access_key: str,
        secret_key: str,
        secure: bool = False,
        region: Optional[str] = None,
    ):
        """
        初始化Minio客户端
        :param endpoint: Minio服务地址
        :param access_key: 访问密钥
        :param secret_key: 密钥
        :param secure: 是否使用HTTPS
        :param region: 区域（可选）
        """
        self.client = Minio(
            endpoint=endpoint,
            access_key=access_key,
            secret_key=secret_key,
            secure=secure,
            region=region,
        )
        logger.info(f"Minio client initialized: {endpoint}")

    def create_bucket(self, bucket_name: str, region: Optional[str] = None) -> bool:
        """
        创建存储桶
        :param bucket_name: 存储桶名称
        :param region: 区域（可选）
        :return: 是否创建成功
        """
        try:
            if not self.client.bucket_exists(bucket_name):
                self.client.make_bucket(bucket_name=bucket_name, region=region)
                logger.info(f"Bucket created: {bucket_name}")
                return True
            logger.info(f"Bucket already exists: {bucket_name}")
            return False
        except S3Error as e:
            logger.error(f"Failed to create bucket {bucket_name}: {e}")
            raise

    def upload_file(
        self,
        bucket_name: str,
        object_name: str,
        file_path: Union[str, Path],
        content_type: Optional[str] = None,
    ) -> bool:
        """
        上传文件
        :param bucket_name: 存储桶名称
        :param object_name: 对象名称（在minio中的路径）
        :param file_path: 本地文件路径
        :param content_type: 内容类型（可选）
        :return: 是否上传成功
        """
        try:
            # 确保存储桶存在
            self.create_bucket(bucket_name)
            
            # 上传文件
            file_path = str(file_path)
            self.client.fput_object(
                bucket_name=bucket_name,
                object_name=object_name,
                file_path=file_path,
                content_type=content_type,
            )
            logger.info(f"File uploaded: {file_path} -> {bucket_name}/{object_name}")
            return True
        except S3Error as e:
            logger.error(f"Failed to upload file {file_path}: {e}")
            raise

    def upload_object(
        self,
        bucket_name: str,
        object_name: str,
        data: BinaryIO,
        length: int,
        content_type: Optional[str] = None,
    ) -> bool:
        """
        上传对象（从内存）
        :param bucket_name: 存储桶名称
        :param object_name: 对象名称
        :param data: 数据（文件对象）
        :param length: 数据长度
        :param content_type: 内容类型（可选）
        :return: 是否上传成功
        """
        try:
            # 确保存储桶存在
            self.create_bucket(bucket_name)
            
            # 上传对象
            self.client.put_object(
                bucket_name=bucket_name,
                object_name=object_name,
                data=data,
                length=length,
                content_type=content_type,
            )
            logger.info(f"Object uploaded: {bucket_name}/{object_name}")
            return True
        except S3Error as e:
            logger.error(f"Failed to upload object {object_name}: {e}")
            raise

    def download_file(
        self,
        bucket_name: str,
        object_name: str,
        file_path: Union[str, Path],
    ) -> bool:
        """
        下载文件
        :param bucket_name: 存储桶名称
        :param object_name: 对象名称
        :param file_path: 本地文件路径
        :return: 是否下载成功
        """
        try:
            file_path = str(file_path)
            # 确保目标目录存在
            os.makedirs(os.path.dirname(file_path), exist_ok=True)
            
            # 下载文件
            self.client.fget_object(
                bucket_name=bucket_name,
                object_name=object_name,
                file_path=file_path,
            )
            logger.info(f"File downloaded: {bucket_name}/{object_name} -> {file_path}")
            return True
        except S3Error as e:
            logger.error(f"Failed to download file {object_name}: {e}")
            raise

    def download_object(
        self,
        bucket_name: str,
        object_name: str,
    ) -> BinaryIO:
        """
        下载对象（到内存）
        :param bucket_name: 存储桶名称
        :param object_name: 对象名称
        :return: 文件对象
        """
        try:
            response = self.client.get_object(
                bucket_name=bucket_name,
                object_name=object_name,
            )
            logger.info(f"Object downloaded: {bucket_name}/{object_name}")
            return response
        except S3Error as e:
            logger.error(f"Failed to download object {object_name}: {e}")
            raise

    def delete_object(self, bucket_name: str, object_name: str) -> bool:
        """
        删除对象
        :param bucket_name: 存储桶名称
        :param object_name: 对象名称
        :return: 是否删除成功
        """
        try:
            self.client.remove_object(
                bucket_name=bucket_name,
                object_name=object_name,
            )
            logger.info(f"Object deleted: {bucket_name}/{object_name}")
            return True
        except S3Error as e:
            logger.error(f"Failed to delete object {object_name}: {e}")
            raise

    def delete_objects(self, bucket_name: str, object_names: list) -> bool:
        """
        批量删除对象
        :param bucket_name: 存储桶名称
        :param object_names: 对象名称列表
        :return: 是否删除成功
        """
        try:
            delete_list = [{'object_name': name} for name in object_names]
            errors = self.client.remove_objects(bucket_name, delete_list)
            for error in errors:
                logger.error(f"Failed to delete object {error.object_name}: {error}")
            logger.info(f"Deleted {len(object_names)} objects from {bucket_name}")
            return True
        except S3Error as e:
            logger.error(f"Failed to delete objects: {e}")
            raise

    def get_object_url(
        self,
        bucket_name: str,
        object_name: str,
        expires: int = 3600,
    ) -> str:
        """
        获取对象的临时URL
        :param bucket_name: 存储桶名称
        :param object_name: 对象名称
        :param expires: 过期时间（秒），默认1小时
        :return: 临时URL
        """
        try:
            url = self.client.presigned_get_object(
                bucket_name=bucket_name,
                object_name=object_name,
                expires=expires,
            )
            logger.info(f"Got presigned URL for {bucket_name}/{object_name}")
            return url
        except S3Error as e:
            logger.error(f"Failed to get presigned URL for {object_name}: {e}")
            raise

    def object_exists(self, bucket_name: str, object_name: str) -> bool:
        """
        检查对象是否存在
        :param bucket_name: 存储桶名称
        :param object_name: 对象名称
        :return: 是否存在
        """
        try:
            self.client.stat_object(bucket_name, object_name)
            return True
        except S3Error as e:
            if e.code == 'NoSuchKey':
                return False
            logger.error(f"Error checking object existence: {e}")
            raise


# 创建一个全局的minio客户端实例（可选）
def get_minio_client(
    endpoint: Optional[str] = None,
    access_key: Optional[str] = None,
    secret_key: Optional[str] = None,
    secure: bool = False,
) -> MinioClient:
    """
    获取Minio客户端实例
    优先使用传入的参数，如果没有则尝试从环境变量获取
    :return: MinioClient实例
    """
    # 从环境变量获取配置
    endpoint = endpoint or os.getenv("MINIO_ENDPOINT")
    access_key = access_key or os.getenv("MINIO_ACCESS_KEY")
    secret_key = secret_key or os.getenv("MINIO_SECRET_KEY")
    
    if not all([endpoint, access_key, secret_key]):
        raise ValueError(
            "MinIO configuration not found. "
            "Please provide endpoint, access_key and secret_key or "
            "set MINIO_ENDPOINT, MINIO_ACCESS_KEY and MINIO_SECRET_KEY environment variables."
        )
    
    return MinioClient(
        endpoint=endpoint,
        access_key=access_key,
        secret_key=secret_key,
        secure=secure,
    )